package com.atguigu.gmall.list.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.list.repository.GoodsRepository;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.model.list.*;
import com.atguigu.gmall.model.product.BaseAttrInfo;
import com.atguigu.gmall.model.product.BaseCategoryView;
import com.atguigu.gmall.model.product.BaseTrademark;
import com.atguigu.gmall.model.product.SkuInfo;
import com.atguigu.gmall.product.client.ProductFeignClient;
import lombok.SneakyThrows;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author mgx
 * @version 1.0
 * @description: TODO
 * @date 2022/11/13 9:31
 */
@Service
@SuppressWarnings("all")
public class SearchServiceImpl implements SearchService {

    @Autowired
    private ProductFeignClient productFeignClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 商品上架
     * @param skuId
     */
    @Override
    public void upperGoods(Long skuId) {
        // 创建封装结果数据
        Goods goods = new Goods();
        // 查询数据
        // 查询SkuInfo
        SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
        if(skuInfo != null) {
            goods.setId(skuId);
            goods.setDefaultImg(skuInfo.getSkuDefaultImg());
            goods.setTitle(skuInfo.getSkuName());
            // 查询分类信息
            BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
            if(categoryView != null) {
                goods.setCategory1Id(categoryView.getCategory1Id());
                goods.setCategory2Id(categoryView.getCategory2Id());
                goods.setCategory3Id(categoryView.getCategory3Id());
                goods.setCategory1Name(categoryView.getCategory1Name());
                goods.setCategory2Name(categoryView.getCategory2Name());
                goods.setCategory3Name(categoryView.getCategory3Name());
            }
            // 查询品牌信息
            BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
            if(trademark != null) {
                goods.setTmId(skuInfo.getTmId());
                goods.setTmName(trademark.getTmName());
                goods.setTmLogoUrl(trademark.getLogoUrl());
            }
        }
        goods.setPrice(productFeignClient.getSkuPrice(skuId).doubleValue());
        goods.setCreateTime(new Date());

        // 查询平台属性信息
        List<BaseAttrInfo> attrList = productFeignClient.getAttrList(skuId);
        // List<BaseAttrInfo> -- List<SearchAttr>
        // BaseAttrInfo -> SearchAttr
        if(!CollectionUtils.isEmpty(attrList)) {
            List<SearchAttr> searchAttrList = attrList.stream().map(baseAttrInfo -> {
                SearchAttr searchAttr = new SearchAttr();
                searchAttr.setAttrId(baseAttrInfo.getId());
                searchAttr.setAttrName(baseAttrInfo.getAttrName());
                searchAttr.setAttrValue(baseAttrInfo.getAttrValueList().get(0).getValueName());
                return searchAttr;
            }).collect(Collectors.toList());
            goods.setAttrs(searchAttrList);
        }
        // 保存到es索引库中
        goodsRepository.save(goods);
    }

    /**
     * 商品下架
     * @param skuId
     */
    @Override
    public void lowerGoods(Long skuId) {
        // 直接从索引库删除即可
        goodsRepository.deleteById(skuId);
    }

    /**
     * 更新商品的热度排名
     * @param skuId
     */
    @Override
    public void incrHotScore(Long skuId) {
        // 定义key
        String hotScoreKey = "hotscore";
        Double hotscore = redisTemplate.opsForZSet().incrementScore(hotScoreKey, "skuId:" + skuId, 1);
        if(hotscore % 10 == 0) {
            // 当到达整10 就更新es中的数据
            Goods goods = goodsRepository.findById(skuId).get();
            goods.setHotScore(Math.round(hotscore));
            // 写回
            goodsRepository.save(goods);
        }

    }


    @Autowired
    private RestHighLevelClient highLevelClient;

    /**
     * 商品检索
     * @param searchParam
     * @return
     */
    @Override
    @SneakyThrows
    public SearchResponseVo search(SearchParam searchParam) {

        // 1. 创建条件对象
        SearchRequest searchRequest = this.buildQuery(searchParam);
        // 2. 执行查询
        SearchResponse searchResponse = highLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        // 3. 根据查询结果对象封装查询结果集
        SearchResponseVo searchResponseVo = this.parseSearchResponseVo(searchResponse);
        // 封装结果集剩下的数据
        searchResponseVo.setPageNo(searchParam.getPageNo());
        searchResponseVo.setPageSize(searchParam.getPageSize());
        // 计算一共有多少页 例如： 8 + 3 -1 / 3 = 3页
        Long totalPages = (searchResponseVo.getTotal() + searchParam.getPageSize() - 1) / searchParam.getPageSize();
        searchResponseVo.setTotalPages(totalPages);
        return searchResponseVo;
    }

    /**
     * 封装结果集数据到结果对象
     * @param searchResponse
     * @return
     */
    private SearchResponseVo parseSearchResponseVo(SearchResponse searchResponse) {
        // 创建封装对象
        SearchResponseVo searchResponseVo = new SearchResponseVo();
        // 聚合数据
        // 获取父聚合对象
        Map<String, Aggregation> aggregationMap = searchResponse.getAggregations().asMap();
        // 获取品牌的聚合对象 -- key是Long类型
        ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
        // 聚合数据-- 品牌
        // 获取聚合数据
        List<? extends Terms.Bucket> tmIdAggBuckets = tmIdAgg.getBuckets();
        if(!CollectionUtils.isEmpty(tmIdAggBuckets)) {
            List<SearchResponseTmVo> trademarkList = tmIdAggBuckets.stream().map(bucket -> {
                // 创建品牌对象
                SearchResponseTmVo searchResponseTmVo = new SearchResponseTmVo();
                // 品牌id
                long tmId = bucket.getKeyAsNumber().longValue();
                searchResponseTmVo.setTmId(tmId);
                // 获取子聚合
                Map<String, Aggregation> subAggregationMap = bucket.getAggregations().asMap();
                ParsedStringTerms tmNameAgg = (ParsedStringTerms) subAggregationMap.get("tmNameAgg");
                // 品牌名
                String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
                searchResponseTmVo.setTmName(tmName);
                // 品牌logoUrl
                ParsedStringTerms tmLogoUrlAgg = (ParsedStringTerms) subAggregationMap.get("tmLogoUrlAgg");
                String logoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
                searchResponseTmVo.setTmLogoUrl(logoUrl);
                return searchResponseTmVo;
            }).collect(Collectors.toList());
            searchResponseVo.setTrademarkList(trademarkList);
        }
        // 聚合查询 -- 平台属性
        ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
        ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
        List<? extends Terms.Bucket> attrIdAggBuckets = attrIdAgg.getBuckets();
        if(!CollectionUtils.isEmpty(attrIdAggBuckets)) {
            List<SearchResponseAttrVo> attrsList = attrIdAggBuckets.stream().map(bucket -> {
                SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
                // 平台属性id
                searchResponseAttrVo.setAttrId(bucket.getKeyAsNumber().longValue());
                Map<String, Aggregation> subAttrIdAgg = bucket.getAggregations().asMap();
                // 平台属性值集合
                ParsedStringTerms attrValueAgg = (ParsedStringTerms) subAttrIdAgg.get("attrValueAgg");
                List<? extends Terms.Bucket> attrValueBuckets = attrValueAgg.getBuckets();
                if(!CollectionUtils.isEmpty(attrValueBuckets)) {
                    List<String> attrValueList = attrValueBuckets.stream().map(attrValueBucket -> {
                        return attrValueBucket.getKeyAsString();
                    }).collect(Collectors.toList());
                    searchResponseAttrVo.setAttrValueList(attrValueList);
                }

                // 平台属性名称
                ParsedStringTerms attrNameAgg = (ParsedStringTerms) subAttrIdAgg.get("attrNameAgg");
                String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
                searchResponseAttrVo.setAttrName(attrName);
                return searchResponseAttrVo;
            }).collect(Collectors.toList());

            // 添加到结果对象中
            searchResponseVo.setAttrsList(attrsList);
        }
        // 封装检索出来的商品信息
        SearchHits searchHits = searchResponse.getHits();
        SearchHit[] hits = searchHits.getHits();
        // 创建商品列表
        List<Goods> goodsList = new ArrayList<>();
        if(hits != null && hits.length > 0) {

            // 遍历查询的结果集
            for (SearchHit hit : hits) {
                // 直接使用JsonObject的方法直接把map转化为goods对象
                Goods goods = JSONObject.parseObject(hit.getSourceAsString(), Goods.class);
                // 需要覆盖title变成带有高亮查询的title
                if(hit.getHighlightFields().get("title") != null) {
                    goods.setTitle(hit.getHighlightFields().get("title").getFragments()[0].toString());
                }

                // 加入列表中
                goodsList.add(goods);
            }
        }
        searchResponseVo.setGoodsList(goodsList);
        // 封装检索出来的数据总量
        searchResponseVo.setTotal(searchHits.getTotalHits().value);
        return searchResponseVo;
    }

    /**
     * 构建条件对象
     * @param searchParam
     * @return
     */
    private SearchRequest buildQuery(SearchParam searchParam) {
        // 创建请求对象 参数：索引库
        SearchRequest searchRequest = new SearchRequest("goods");
        // 构建条件对象
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();

        // 创建多条件查询对象
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        if(!StringUtils.isEmpty(searchParam.getKeyword())) {
            // 匹配关键字查询
            MatchQueryBuilder title = QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND);
            boolQueryBuilder.must(title);
        }
        // 过滤查询 -- 品牌 2:华为
        String trademark = searchParam.getTrademark();
        if(!StringUtils.isEmpty(trademark)) {
            // 分开
            String[] split = trademark.split(":");
            TermQueryBuilder tmId = QueryBuilders.termQuery("tmId", split[0]);
            boolQueryBuilder.filter(tmId);
        }

        // 过滤查询 -- 分类
        if(!StringUtils.isEmpty(searchParam.getCategory1Id())) {
            TermQueryBuilder category1Id = QueryBuilders.termQuery("category1Id", searchParam.getCategory1Id());
            boolQueryBuilder.filter(category1Id);
        }
        if(!StringUtils.isEmpty(searchParam.getCategory2Id())) {
            TermQueryBuilder category2Id = QueryBuilders.termQuery("category2Id", searchParam.getCategory2Id());
            boolQueryBuilder.filter(category2Id);
        }
        if(!StringUtils.isEmpty(searchParam.getCategory3Id())) {
            TermQueryBuilder category3Id = QueryBuilders.termQuery("category3Id", searchParam.getCategory3Id());
            boolQueryBuilder.filter(category3Id);
        }

        // 过滤查询 -- 平台属性
        String[] props = searchParam.getProps();
        if(props != null && props.length > 0) {
            // 循环遍历
            for (String prop : props) {
                // props=23:4G:运行内存
                //平台属性Id 平台属性值名称 平台属性名
                String[] split = prop.split(":");
                if(split != null && split.length == 3) {
                    BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
                    BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
                    // filter是单条件，此处使用must多条件
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", split[0]));
                    subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", split[1]));
                    boolQuery.must(QueryBuilders.nestedQuery("attrs", subBoolQuery, ScoreMode.None));
                    // 把过滤条件对象加到最外层多条件查询对象中
                    boolQueryBuilder.filter(boolQuery);
                }
            }
        }

        searchSourceBuilder.query(boolQueryBuilder);

        // 聚合查询 -- 品牌
        TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg").field("tmId");
        TermsAggregationBuilder tmNameAgg = AggregationBuilders.terms("tmNameAgg").field("tmName");
        TermsAggregationBuilder tmLogoUrlAgg = AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl");
        tmIdAgg.subAggregation(tmNameAgg);
        tmIdAgg.subAggregation(tmLogoUrlAgg);
        searchSourceBuilder.aggregation(tmIdAgg);

        // 聚合查询 -- 平台属性
        NestedAggregationBuilder attrAgg = AggregationBuilders.nested("attrAgg", "attrs");
        // 一级子聚合
        TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg").field("attrs.attrId");
        // 一级子聚合添加到父级Nested聚合中
        attrAgg.subAggregation(attrIdAgg);
        // 二级子聚合
        TermsAggregationBuilder attrNameAgg = AggregationBuilders.terms("attrNameAgg").field("attrs.attrName");
        TermsAggregationBuilder attrValueAgg = AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue");
        // 二级子聚合和一级子聚合构建关系
        attrIdAgg.subAggregation(attrNameAgg);
        attrIdAgg.subAggregation(attrValueAgg);
        searchSourceBuilder.aggregation(attrAgg);

        // 分页查询
        Integer pageNo = searchParam.getPageNo();
        Integer pageSize = searchParam.getPageSize();
        // from 计算
        int index = (pageNo - 1) * pageSize;
        searchSourceBuilder.from(index);
        searchSourceBuilder.size(pageSize);

        // 排序查询
        // 1：综合排序/热度  2：价格
        // 1:desc【真实参数】   1:hotScore 2:price -- 序号表示排序字段
        String order = searchParam.getOrder();
        if(!StringUtils.isEmpty(order)) {
            String[] split = order.split(":");
            String field = null;
            switch (split[0]){
                case "1":
                    field = "hotScore";
                    break;
                case "2":
                    field = "price";
                    break;
            }
            searchSourceBuilder.sort(field, split[1].equals("asc")? SortOrder.ASC : SortOrder.DESC);
        }else {
            // 默认按照热度降序排序
            searchSourceBuilder.sort("hotScore", SortOrder.DESC);
        }


        // 高亮查询
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.field("title");
        highlightBuilder.preTags("<span style=color:red>");
        highlightBuilder.postTags("</span>");
        searchSourceBuilder.highlighter(highlightBuilder);

        // 过滤查询结果集字段查询
        searchSourceBuilder.fetchSource(new String[]{"id", "defaultImg", "title", "price"}, null);

        // 把构建对象添加到请求对象中
        searchRequest.source(searchSourceBuilder);
        System.out.println("dsl:=="+searchSourceBuilder.toString());
        return searchRequest;
    }
}
